Welcome! While we're waiting:
Please download the workshop files from: https://github.com/dlab-geo/r-leaflet-workshop
Unzip the Zipfile: https://github.com/dlab-geo/r-leaflet-workshop/archive/master.zip
Open RStudio
Open a new R script file
May 2018
Welcome! While we're waiting:
Please download the workshop files from: https://github.com/dlab-geo/r-leaflet-workshop
Unzip the Zipfile: https://github.com/dlab-geo/r-leaflet-workshop/archive/master.zip
Open RStudio
Open a new R script file
Basic Maps
Data Maps
Doing More
This workshop/tutorial will walk you through the basics of using the Leaflet mapping package in R. You can follow along in any of the three formats:
Make sure you can copy and paste from one of the above into the script.
To begin, lets set up our packages and environment.
Load the packages we will use today
library(leaflet) library(RColorBrewer) library(sp) library(rgdal) library(htmlwidgets) library(magrittr) # or dplyr
Install any packages that you do not have on your computer
# install.packages("leaflet")
# install.packages("RColorBrewer")
# install.packages("sp")
# install.packages("rgdal")
# install.packages("htmlwidgets")
# install.packages("magrittr") # or dplyr
to the folder in which you unzipped the workshop files
Leaflet is a lightweight, yet powerful javascript library for creating interactive web maps.

Leaflet maps are a combination of HTML and Javascript code that is meant to be rendered in a web browser.
We can use the R leaflet package to create Leaflet maps in R
map1 <- leaflet() # Initialize the map object map1 <- addTiles(map1) # Add basemap tiles map1 # Display the map
map1 <- leaflet() # Initialize the map object map1 <- addTiles(map1) # Add basemap tiles - default is OpenStreetMap map1 # Display the map
We specify the center and zoom level for the map
map1 <- leaflet() %>%
addTiles() %>%
setView(lat=37.870044, lng=-122.258169, zoom = 15)
map1
map1 # setView(lat=37.870044, lng=-122.258169, zoom = 15)
Requires dplyr or magrittr package to be loaded
map2 <- leaflet() %>%
addTiles() %>%
setView(lat=37.870044, lng=-122.258169, zoom = 15)
map2
map2
Regular
map1 <- leaflet() map1 <- addTiles(map1) map1 <- setView(map1, lat=37.870044, lng=-122.258169, zoom = 15) map1
Piping
map2 <- leaflet() %>%
addTiles() %>%
setView(lat=37.870044, lng=-122.258169, zoom = 15)
map2
mapRerun the piping code changing the zoom level
By Default, Leaflet uses the OpenStreetMap basemap, which is added with the addTiles() function
leaflet() %>% addTiles() %>%
setView(lat=37.870, lng=-122.258, zoom = 15)
Use addProviderTiles with the name of the basemap to add a different basemap.
Below, we are using the ESRI World Street Map basemap.
map2 <- leaflet() %>%
addProviderTiles("Esri.WorldStreetMap") %>%
setView(lat=37.870044, lng=-122.258169, zoom = 12)
map2 #Using ESRI WorldStreetMap basemap
Try adding a different basemap.
For example, "CartoDB.Positron"
And open the web page of available basemaps
http://leaflet-extras.github.io/leaflet-providers/preview/
For more info, read the documentation
?addProviderTiles
leaflet() %>% addProviderTiles("CartoDB.Positron") %>%
setView(lat=37.870044, lng=-122.258169, zoom = 12)
You can add an online, georectified scanned map to leaflet.
There are many of these online at the New York Public Library or MapWarper, and other websites.
Let's add this map of Berkeley in 1880
Here we are combining addTiles and addProviderTiles
mapurl <- "https://mapwarper.net/maps/tile/25477/{z}/{x}/{y}.png"
map2 <- leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
addTiles(mapurl) %>% # custom map image
setView(lat=37.870044, lng=-122.258169, zoom = 13)
map2 # Map of Berkeley, 1880 overlaid on the CartoDB basemap
Use addMarkers to add one or more data points to the map.
The map will automatically center on the data and determine an appropriate zoom level - if you don't use setView (commented out below).
You can override this by using setView.
map3 <- leaflet() %>% addTiles() %>% # Add default OpenStreetMap map tiles #setView(lat=37.870044, lng=-122.258169, zoom = 17) %>% addMarkers(lat=37.870044, lng=-122.258169, popup="Go Bears!")
map3 # Display the map - Click on the marker
San Francisco Open Data Portal
This data set includes the Office of the Assessor-Recorder’s secured property tax roll spanning from 2015.
We are using this as a proxy for home values.
We are working with a simplified sample of the full data set.
Set your working directory first to the folder where you downloaded the workshop files!
sfhomes <- read.csv('data/sfhomes15.csv', stringsAsFactors = FALSE)
str(sfhomes)
## 'data.frame': 680 obs. of 11 variables: ## $ SalesDate : chr "2015-08-21" "2015-08-13" "2015-12-29" "2015-07-06" ... ## $ Address : chr "0000 2760 19TH AV0015" "0000 0560AMISSOURI ST0000" "0000 0718 LONG BRIDGE ST1202" "0000 0899 VALENCIA ST0202" ... ## $ YrBuilt : int 1979 2003 2016 2015 1961 1900 2015 NA 1947 1907 ... ## $ NumBeds : int 2 2 2 3 3 2 2 0 0 3 ... ## $ NumBaths : int 2 2 2 3 3 2 2 0 0 3 ... ## $ NumUnits : int 1 1 1 1 1 1 1 1 1 1 ... ## $ AreaSqFt : int 1595 1191 1346 1266 1840 1256 1520 536 950 1837 ... ## $ Neighborhood: chr "Twin Peaks" "Potrero Hill" "Mission Bay" "Mission" ... ## $ Value : int 865000 1402560 2260993 1700000 2309692 2700564 1925000 583768 944180 1750001 ... ## $ lat : num 37.7 37.8 37.8 37.8 37.8 ... ## $ lon : num -122 -122 -122 -122 -122 ...
head(sfhomes)
## SalesDate Address YrBuilt NumBeds NumBaths ## 1 2015-08-21 0000 2760 19TH AV0015 1979 2 2 ## 2 2015-08-13 0000 0560AMISSOURI ST0000 2003 2 2 ## 3 2015-12-29 0000 0718 LONG BRIDGE ST1202 2016 2 2 ## 4 2015-07-06 0000 0899 VALENCIA ST0202 2015 3 3 ## 5 2015-06-12 0000 1333 JONES ST0808 1961 3 3 ## 6 2015-04-14 0000 1904 BAKER ST0000 1900 2 2 ## NumUnits AreaSqFt Neighborhood Value lat lon ## 1 1 1595 Twin Peaks 865000 37.73601 -122.4741 ## 2 1 1191 Potrero Hill 1402560 37.75920 -122.3965 ## 3 1 1346 Mission Bay 2260993 37.77181 -122.3942 ## 4 1 1266 Mission 1700000 37.75876 -122.4210 ## 5 1 1840 Nob Hill 2309692 37.79362 -122.4149 ## 6 1 1256 Pacific Heights 2700564 37.78881 -122.4437
map4 <- leaflet() %>%
addTiles() %>%
addMarkers(lat=sfhomes$lat, lng=sfhomes$lon,
popup= paste("Address:", sfhomes$Address,
"<br>", # add line break
"Property Value: ", sfhomes$Value))
map4 # How did we map the data frame?
We can save the popup code and re-use it instead of typing it over and over again.
popup_content <- paste("<b>Address:</b>", sfhomes$Address,"<br>",
"<b>Property Value</b>: ", sfhomes$Value, "<br>",
"<b>Neighborhood:</b> ", sfhomes$Neighborhood, "<br>",
"<b>Num Bedrooms: </b>", sfhomes$NumBeds, "<br>",
"<b>Num Bathrooms:</b>", sfhomes$NumBaths
)
map4 <- leaflet() %>%
addTiles() %>%
addMarkers(lat=sfhomes$lat, lng=sfhomes$lon,
popup= popup_content)
leaflet() %>% addTiles() %>%
addMarkers(lat=sfhomes$lat, lng=sfhomes$lon, popup= popup_content)
Instead of this:
leaflet() %>% addTiles() %>% addMarkers(lat=sfhomes$lat, lng=sfhomes$lon, popup= popup_content)
We can use this syntax:
leaflet(sfhomes) %>% addTiles() %>% addMarkers(~lon, ~lat, popup = popup_content)
By passing in the name of the data object (sfhomes) to leaflet we can reference column names directly.
Below we are using the tilde (~) operator to map the data to the addMarkers function.
When the addMarkers function arguments lng= and lat= are not named they must be in the expected order (lng, lat).
leaflet(sfhomes) %>% addTiles() %>% addMarkers(~lon, ~lat, popup = popup_content)
The map is too crowded with Markers.
Read the addMarker documentation for options to address this.
addMarkers(map, lng = NULL, lat = NULL, layerId = NULL,
group = NULL, icon = NULL, popup = NULL,
options = markerOptions(),
clusterOptions = NULL, clusterId = NULL,
data = getMapData(map))
map4 <- leaflet(sfhomes) %>%
addTiles() %>%
addMarkers(~lon, ~lat, popup= popup_content,
clusterOptions = 1)
map4 # Explore the Map - hover over a cluster marker, zoom in.
addCircleMarker
map4 <- leaflet(sfhomes) %>% addTiles() %>% addCircleMarkers(~lon, ~lat, popup = popup_content)
addCircleMarker
map4
addCircleMarkers(map, lng = NULL, lat = NULL, radius = 10,
layerId = NULL, group = NULL, stroke = TRUE, color = "#03F",
weight = 5, opacity = 0.5,
fill = TRUE, fillColor = color, ....)
Change color, radius and stroke weight of circle markers
map4 <- leaflet(sfhomes) %>%
addTiles() %>%
addCircleMarkers(~lon, ~lat, popup = popup_content,
color="white", radius=6, weight=2, # stroke
fillColor="red",fillOpacity = 0.75 # fill
)
colors() to see a list of all R named colors.map4
Art + Science
Finding the right symbology - size, color, shape, etc
and mapping it to your data
Often requires a classification scheme
We can symbolize the size of points by data values by making the radius of the circle a function of a data value.
map4 <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat, popup=popup_content,
fillColor= NA, color="Red", weight=1, fillOpacity = 0,
radius= ~NumBeds+2
)
map4 # Size is a function of what variable?
Circles and CircleMarkers look quite similar.
Circle radii are specified in meters while CircleMarkers are specified in pixels.
map4b <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircles(~lon, ~lat, popup=popup_content,
fillColor= NA, color="Red",
weight=1, fillOpacity = 0,
radius= ~NumBeds*10
)
map4b # Compare map4 and map4b at different zoom levels
The RColorBrewer package is widely used to create color palettes for maps.
There are 3 different types of color palettes
Contrasting colors for categorical data
display.brewer.all(type="qual")
display.brewer.pal(7, "Set3" ) # Try a different number of colors
For highlighting trends in numerical data
display.brewer.all(type="seq")
For highlighting the outliers
display.brewer.all(type="div")
Let's map sfhomes by the values in the Neighborhood column.
First, check out the RColorBrewer qualitative color palettes
display.brewer.all(type="qual")
leaflet::colorfactor takes as input a color palette and a domain that contains the full range of possible values to be mapped.
colorFactor(palette, domain, levels = NULL, ordered = FALSE, na.color = "#808080", alpha = FALSE, reverse = FALSE)
colorfactor returns a function specific to that domain that can be used to output a set of color values.
# Create a qualitative color palette
myColors <- colorFactor("Paired", sfhomes$Neighborhood)
myColors <- colorFactor("Paired", sfhomes$Neighborhood)
the_color_values <- myColors(sfhomes$Neighborhood)
length(the_color_values)
## [1] 680
length(the_color_values) == length(sfhomes$Neighborhood)
## [1] TRUE
unique(the_color_values)
## [1] "#B15928" "#C4AAD2" "#E83F26" "#F57166" "#FBAE63" "#F6905B" "#3EA433" ## [8] "#BFA19E" "#EDD279" "#4F8EC1" "#9BD277" "#FF9939" "#A6CEE3" "#7E54A6" ## [15] "#79A6A3" "#C3A176"
Using a color palette
map4 <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat,
popup= popup_content,
fillColor= ~myColors(Neighborhood),
radius=6, color=NA, weight=2, fillOpacity = 1
)
map4 # what neighborhood has the most 2015 transactions?
map4 <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat, popup=popup_content,
fillColor= ~myColors(Neighborhood),
radius=6, color=NA, weight=2,fillOpacity = 1
) %>%
addLegend(title = "Neighborhood", pal = myColors,
values = ~Neighborhood, opacity = 1,
position="bottomleft")
map4
Let's map the homes by value.
First, check out the sequential color palettes
display.brewer.all(type="seq")
For simple linear scaling of colors to values use colorNumeric.
proportional color mapcolorNumeric(palette, domain, na.color = "#808080", alpha = FALSE, reverse = FALSE)
Create the numeric color mapping function
numColors <- colorNumeric("Reds", sfhomes$Value)
Apply the numColors color mapping function to create a proportional color map
map4 <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat, popup=popup_content,
fillColor= ~numColors(Value),
radius=6, color="grey", weight=1, fillOpacity = 1
) %>%
addLegend(title = "Property Values", pal = numColors,
values = ~Value, opacity = 1,
position="bottomleft")
map4 # proportional color map
You can use colorQuantile to create a color palette based on quantile binning of the data. This is used to create a graduated color map.
colorQuantile(palette, domain, n = 4, probs = seq(0, 1, length.out = n + 1), na.color = "#808080", alpha = FALSE, reverse = FALSE)
The default is 4 bins, but you can set n manually (3 to 7)
Note: The colorBin function can be used to create color palettes based on different classification methods for binning the data, eg equal interval, natural breaks etc.
# Use colorQuantile to create a color function for the data
quantColors <- colorQuantile("Reds", sfhomes$Value, n=5)
# Use the color function in the leaflet map
map4b <- leaflet(sfhomes) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat, popup=popup_content,
fillColor= ~quantColors(Value),
radius=6, color="grey", weight=1,fillOpacity = 1
)
map4b # Graduated color map
map4b %>% addLegend(title = "Value", pal = quantColors,
values = ~Value, opacity = 1,
position="bottomleft")
map5 <-map4b %>% addLegend(pal = quantColors, values = ~Value,
title = "Property Value, 2015",
position="bottomleft",
opacity=1,
labFormat = function(type, cuts, p) {
n = length(cuts)
cuts = paste0("$", format(cuts[-n], big.mark=","),
" - ", "$",format(cuts[-1], big.mark=","))
}
)
map5 # Graduated Color Map
What's happening here?
sfhomes_low2high <- sfhomes[order(sfhomes$Value, decreasing = FALSE),]
map5 <- leaflet(sfhomes_low2high) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(~lon, ~lat, popup=popup_content,
fillColor= ~quantColors(Value),
radius=6, color="grey", weight=1,fillOpacity = 1
)
map5 # points reordered from low to high value
Basic Maps
addMarkers - Simple Marker MapsaddCircleMarkers - Circle Marker MapsData Maps
addCircles Proportional symbol mapscolorFactor - Category MapscolorNumeric - Proportional color mapscolorQuantile - Graduated color mapsWe have been working with geographic data in data frames
The coordinates are longitude and latitude
Next up, more complex spatial objects
We can use the sp and rgdal packages to import, manipulate and map more complex spatial objects.
sp - R classes and methods for spatial data
rgdal - Functions for importing and transforming spatial data
Let's use these to import data in ESRI Shapefiles
Read in the data
sf_md_hhi <- readOGR(dsn="data",layer="sf_medhhincome_acs5y_16")
## OGR data source with driver: ESRI Shapefile ## Source: "data", layer: "sf_medhhincome_acs5y_16" ## with 196 features ## It has 5 fields
summary(sf_md_hhi)
## Object of class SpatialPolygonsDataFrame ## Coordinates: ## min max ## x -123.01392 -122.32756 ## y 37.69274 37.86334 ## Is projected: FALSE ## proj4string : ## [+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0] ## Data attributes: ## GEOID ## 06075010100: 1 ## 06075010200: 1 ## 06075010300: 1 ## 06075010400: 1 ## 06075010500: 1 ## 06075010600: 1 ## (Other) :190 ## NAME variable ## Census Tract 101, San Francisco County, California: 1 B19013_001:196 ## Census Tract 102, San Francisco County, California: 1 ## Census Tract 103, San Francisco County, California: 1 ## Census Tract 104, San Francisco County, California: 1 ## Census Tract 105, San Francisco County, California: 1 ## Census Tract 106, San Francisco County, California: 1 ## (Other) :190 ## estimate moe ## Min. : 11971 Min. : 739 ## 1st Qu.: 65893 1st Qu.: 9744 ## Median : 89668 Median :15343 ## Mean : 91151 Mean :16211 ## 3rd Qu.:117922 3rd Qu.:20032 ## Max. :205865 Max. :89454 ## NA's :2 NA's :2
Leaflet understands sp objects!!!
Map sf_md_hhi with addPolygons
map6 <- leaflet() %>% addTiles() %>% addPolygons(data=sf_md_hhi)
map6 # using addPolygons to map sf_md_hhi
?addPolygons
addPolygons(map, lng = NULL, lat = NULL, layerId = NULL, group = NULL,
stroke = TRUE, color = "#03F", weight = 5, opacity = 0.5,
fill = TRUE, fillColor = color, fillOpacity = 0.2,
dashArray = NULL, smoothFactor = 1, noClip = FALSE,
popup = NULL, popupOptions = NULL, label = NULL,
labelOptions = NULL, options = pathOptions(),
highlightOptions = NULL, data = getMapData(map))
map6 <- leaflet() %>%
setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
addProviderTiles("CartoDB.Positron") %>%
# Customize the symbology of the polygons
addPolygons(data=sf_md_hhi, color="grey", weight=1,
fillColor="Orange", fillOpacity = 0.25)
map6 # color="grey", weight=1, fillColor="Orange", fillOpacity = 0.25
Color regions based on data values.
The data values are classified into bins.
Each bin gets a unique color from a color palette.
Median Household Income is in the estimate column
Recipe:
estimate#display.brewer.all(type="seq")
Then create the color mapping function
##
quantColors <- colorQuantile("YlOrRd", sf_md_hhi$estimate, n=5)
A choropleth map is a graduated color map of polygon data.
map6 <- leaflet() %>%
setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
addProviderTiles("CartoDB.Positron") %>%
#
addPolygons(data=sf_md_hhi,
color="white",
weight=1,
opacity=0.5,
fillColor=~quantColors(estimate),
fillOpacity = 0.65,
popup = paste0("$",sf_md_hhi$estimate))
map6 # choropleth map of median household income by census tract
map6 <- map6 %>% addLegend(pal = quantColors,
values = sf_md_hhi$estimate,
title = "Median HH Income",
position="bottomleft",
opacity=1,
labFormat = function(type, cuts, p) {
n = length(cuts)
cuts = paste0("$", format(cuts[-n], big.mark=","),
" - ", "$",format(cuts[-1], big.mark=","))
}
)
map6
You can add multiple data layers to a leaflet map.
Let's add the sfhomes to the map
cheap <- sfhomes[sfhomes$Value < 1000000,]
map7 <- leaflet() %>%
setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
addProviderTiles("CartoDB.Positron") %>%
# Median household income polygons
addPolygons(data=sf_md_hhi, color="white", weight=1, opacity=0.5,
fillColor=~quantColors(estimate), fillOpacity = 0.65,
popup = paste0("$",sf_md_hhi$estimate)) %>%
# sfhomes points
addCircleMarkers(data=cheap, popup=paste0("$",cheap$Value),
color="black",weight=1, radius=6,
fillColor="white", fillOpacity = 0.75)
map7 # sfhomes and median household income
We can add a layer switcher control with addLayersControl().
This allows us to toggle on/off the display of a map layer.
First, we need to assign a group to each map layer
?addLayersControl
map8 <- leaflet() %>%
setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
addProviderTiles("CartoDB.Positron") %>%
addPolygons(data=sf_md_hhi, color="white", weight=1, opacity=0.5,
fillColor=~quantColors(estimate), fillOpacity = 0.65,
popup = paste0("$",sf_md_hhi$estimate),
group="Median HH Income"
) %>%
addCircleMarkers(data=cheap, popup=paste0("$",cheap$Value),
color="black",weight=1, radius=6,
fillColor="white", fillOpacity = 0.75,
group="Property Values"
) %>%
addLayersControl(
overlayGroups = c("Property Values","Median HH Income"),
options = layersControlOptions(collapsed = FALSE)
)
map8
map8 <- leaflet() %>%
setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
addProviderTiles("CartoDB.Positron", group="Simple Basemap") %>%
addProviderTiles("Esri.WorldStreetMap", group="Streets Basemap") %>%
addTiles("", group="No Basemap") %>%
#
addPolygons(data=sf_md_hhi, color="white", weight=1, opacity=0.5,
fillColor=~quantColors(estimate), fillOpacity = 0.65,
popup = paste0("$",sf_md_hhi$estimate),
group="Median HH Income"
) %>%
addCircleMarkers(data=cheap, popup=paste0("$",cheap$Value),
color="black",weight=1, radius=6,
fillColor="white", fillOpacity = 0.75,
group="Property Values"
) %>%
addLayersControl(
baseGroups = c("Simple Basemap", "Streets Basemap", "No Basemap"),
overlayGroups = c("Property Values","Median HH Income"),
options = layersControlOptions(collapsed = FALSE)
)
map8
Interactive Maps in R - check
Web Maps can be shared if online.
Easy way to go is RPubs
You can share you map online by publishing it to RPubs.
RPubs account to make that work.Enter the code for your map in the console
In the Viewer window, click on the Publish icon.
Another way to share your map is to save it to a file. You can then email it, host it on your own web server or host it on github, etc.
#library(htmlwidgets) saveWidget(map7, file="testmap.html")
Open your file to by double-clicking on it in the Mac Finder or Windows Explorer.
Getting more Practice
tmap
sp objects within Rleaflet for creating Leaflet maps as well as publication ready static map images.mapview
tmap. I haven't checked it out but I keep seeing it so I think it is emerging.leafletPowerful Highly customizable tool for creating great interactive maps.
Integrates with Shiny, an R web Framework from the creators of RStudio
http://www.shinyapps.io/ A freemium web platform for hosting your shiny apps.
https://pfrontiera.shinyapps.io/leaflet_shiny_test/
Files are in the tutorial folder if you want to see how this app was created.
https://rmarkdown.rstudio.com/flexdashboard/
A tool for creating Shiny dashboards via RMarkdown. A little bit easier but less customizable than Shiny.
Demo w/Tutorial at https://cengel.shinyapps.io/RioSlaveMarket/
To you!
and to Josh Pepper who did an earlier workshop on which these materials are loosely based.